//日志落地模块
//功能:将格式化完成后的日志消息字符串,输出到指定位置
//扩展:支持同时将日志落地到不同位置
//位置分类
//1.    标准输出
//2.    指定文件
//3.    滚动文件(文件按照时间/大小进行滚动切换)
//扩展:支持落地的方向的扩展
//实现思想
//1.抽象落地基类
//2.派生子类(根据不同的落地方向进行派生)
//3.使用工厂模式进行创建和分离

#ifndef __M_SINK_H__
#define __M_SINK_H__ 

#include<memory>
#include<ctime>
#include<fstream>
#include<cassert>
#include<sstream>
#include<functional>
#include"util.hpp"
#include"buffer.hpp"


namespace zllog
{
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink()
        {}
        virtual ~LogSink()
        {}
        virtual void log(const char* data,size_t len) = 0;
    };
    //落地方向:标准输出
    class StdoutSink : public LogSink
    {
    public:
        StdoutSink()
        {}
        ~StdoutSink()
        {}
        void log(const char* data,size_t len)
        {
            std::cout.write(data,len);
        }
    private:
    };
    //落地方向:指定文件
    class FileSink : public LogSink
    {
    public:
    //构造时传入文件名,并打开文件,将操作句柄管理
        FileSink(const std::string& pathname)
            :_pathname(pathname)
        {
            //1.创建文件所在的目录
            util::File::createDirectory(util::File::Path(_pathname));
            //2.创建并打开日志文件
            _ofs.open(_pathname,std::ios_base::binary | std::ios_base::app);
            assert(_ofs.is_open());
        }
        ~FileSink()
        {}
        //将日志消息写入到标准输出
        void log(const char* data,size_t len)
        {
            _ofs.write(data,len);
            assert(_ofs.good());
        }
    private:
        std::string _pathname;
        std::ofstream _ofs;
    };
    //落地方向:滚动文件(以大小进行滚动)
    class RollBySizeSink : public LogSink
    {
    public:
        RollBySizeSink(const std::string& basename,size_t max_fsize)
            :_basename(basename)
            ,_max_fsize(max_fsize)
            ,_cur_fsize(0)
            ,_name_count(0)
        {

            //1.创建文件所在的目录
            std::string pathname = createNewFile();
            util::File::createDirectory(util::File::Path(pathname));

            //2.创建并打开日志文件
            _ofs.open(pathname,std::ios_base::binary | std::ios_base::app);
            assert(_ofs.is_open());
        }
        ~RollBySizeSink()
        {}
        //将日志消息写入到标准输出--写入前判断文件大小,超过了最大大小就要切换文件
        //在文件打开时调用log
        void log(const char* data,size_t len)
        {
            if(_cur_fsize >= _max_fsize)
            {
                _ofs.close();
                std::string pathname = createNewFile();
                //1.创建文件所在的目录
                util::File::createDirectory(util::File::Path(pathname));

                //2.创建并打开日志文件
                _ofs.open(pathname,std::ios_base::binary | std::ios_base::app);
                assert(_ofs.is_open());
                _cur_fsize = 0;
            }
            //********************* 
            //当使用ASYNC进行写入时,如果从缓冲区一次性获取的数据大于_max_fsize - _cur_fsize,就坑了
            //无法控制按文件大小滚动切换

            if(len > _max_fsize - _cur_fsize)
            {
                for(int i= 0;i < len;i++)
                {
                    _ofs.write(data + i,1);
                    assert(_ofs.good());
                    _cur_fsize += 1;

                    if(_cur_fsize >= _max_fsize)
                    {
                        _ofs.close();
                        std::string pathname = createNewFile();
                        //1.创建文件所在的目录
                        util::File::createDirectory(util::File::Path(pathname));

                        //2.创建并打开日志文件
                        _ofs.open(pathname,std::ios_base::binary | std::ios_base::app);
                        assert(_ofs.is_open());
                        _cur_fsize = 0;
                    }
                }
                return;
            }
            _ofs.write(data,len);
            assert(_ofs.good());
            _cur_fsize += len;
        }
    private:
        std::string createNewFile()    //进行大小判断,超过指定大小就创建新文件
        {
            //获取系统时间,以系统时间来构造文件名扩展名
            time_t t = util::Data::now();   //time_t 就是long int
            struct tm lt;
            localtime_r(&t,&lt);
            std::stringstream filename;
            filename<<_basename<<lt.tm_year + 1900<<lt.tm_mon + 1<<lt.tm_mday\
            <<lt.tm_hour<<lt.tm_min<<lt.tm_sec<<"-"<<_name_count++;
            filename<<".log";
            return filename.str();

        }
    private:
    //基础文件名 + 扩展文件名 组成一个实际的当输出的文件名
        std::string _basename;   //.logs/base
        std::ofstream _ofs; 
        size_t _max_fsize;       //记录最大大小,当前文件超过了这个大小就要切换文件
        size_t _cur_fsize;       //记录当前文件已经写入的数据大小
        size_t _name_count;      //计数器

    };
    

    //落地方向:滚动文件(以时间段进行滚动)
    //以时间进行文件滚动,实际上是以时间段进行滚动
        //实现思想:以当前系统时间,取模时间段大小,可以得到当前时间段是第几个时间段
            //每次以当前系统时间取模,判断与当前文件的时间段是否一致,不一致则代表不是一个时间段
    enum class TimeGap
    {
        GAP_SECOND,
        GAP_MINUTE,
        GAP_HOUR,
        GAP_DAY
    };

    class RollByTimeSink : public LogSink
    {
    public:
        RollByTimeSink(const std::string& basename,TimeGap gap_type)
            :_basename(basename)
        {
            switch(gap_type)
            {
                case TimeGap::GAP_SECOND: _gap_size = 1; break;
                case TimeGap::GAP_MINUTE: _gap_size = 60; break;
                case TimeGap::GAP_HOUR: _gap_size = 3600; break;
                case TimeGap::GAP_DAY: _gap_size = 3600 * 60; break;
            }
            //1.创建文件所在的目录
            std::string pathname = CreateNewFile();
            util::File::createDirectory(util::File::Path(pathname));
            _cur_gap = _gap_size == 1 ? util::Data::now() : util::Data::now() % _gap_size;
            //2.创建并打开日志文件
            _ofs.open(pathname,std::ios_base::binary | std::ios_base::app);
            assert(_ofs.is_open());

        }
        void log(const char* data,size_t len)
        {
            time_t cur = util::Data::now();
            if(_cur_gap != cur % _gap_size)
            {
                _ofs.close();
                //1.创建文件所在的目录
                std::string pathname = CreateNewFile();
                util::File::createDirectory(util::File::Path(pathname));
                //2.创建并打开日志文件
                _ofs.open(pathname,std::ios_base::binary | std::ios_base::app);
                assert(_ofs.is_open());
            }
            _ofs.write(data,len);
            assert(_ofs.good());
        }
        std::string CreateNewFile()
        {
            time_t t = util::Data::now();
            struct tm lt;
            localtime_r(&t,&lt);
            std::stringstream filename;
            filename<<_basename<<lt.tm_year + 1900<<lt.tm_mon + 1<<lt.tm_mday\
            <<lt.tm_hour<<lt.tm_min<<lt.tm_sec<<".log";
            return filename.str();
        }
    private:
        std::string _basename;
        std::ofstream _ofs;
        size_t _gap_size;   //时间段的大小
        size_t _cur_gap;    //当前是第几个时间段
    }; 



    //模块落地工厂:
    class SinkFactory
    {
    public:
        template<typename SinkType,typename ...Args>
        static LogSink::ptr create(Args&& ...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
        
    };
};

#endif